package engine;
import gameobject.Object;
import gameobject.Room;
import gameobject.User;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.List;
import java.awt.Rectangle;
import java.awt.TextArea;
import java.awt.event.MouseMotionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;

import tree.Node;
import tree.Tree;

/**
 * Class for the window and GUI.
 * 
 * @author Simon Dehne
 *
 */
public class MainFrame extends JFrame {
	
	boolean pathFound = false;
	ArrayList<Node> upscaledPath = new ArrayList<Node>();
	
	TextArea textField;
	Image roomImage;
	ImageIcon roomImageIcon;
	MouseEventHandler mouseHandler;
	MouseMotionListener mouseMotionHandler;
	Room currentRoom;
	Map<String, Object> roomItems;
	Map<String, Object> playerItems;
	
	// for objects in the room
	ImageIcon objectImageIcon;
	Image objectImage;
	
	// for inventory
	ImageIcon invImageIcon1;
	Image invImage1;
	ImageIcon invImageIcon2;
	Image invImage2;
	ImageIcon invImageIcon3;
	Image invImage3;
	ImageIcon invImageIcon4;
	Image invImage4;
	ImageIcon invImageIcon5;
	Image invImage5;
	
	Object item1;
	Object item2;
	Object item3;
	Object item4;
	Object item5;	
	
	// variables for off-screen rendering
	Graphics dbg;
	Image dbImage = null;
	
	User player;
	
	/**
	 * Constructor, sets the title, size and other various options of the window.
	 * @throws IOException 
	 */
	public MainFrame() throws IOException {
		
		// Sets options of the window
		this.setTitle("JLeibniz");
		this.setSize(1024, 768);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		this.setResizable(false);
		this.setLayout(null);
		
		if(JLeibniz.isDebug) JLeibniz.outWriter.write("Options for mainFrame set.\n");
		
		
		/* This is for the text window
		 * First, create it and add it to the main window.
		 * Also set the layout manager to null because we don't want any strange
		 * behaviour.
		 * 
		 * Then, we set it to the position we want it to be placed (27, 650,
		 * I don't know why the pixels are so ugly, the image is placed at x position
		 * 30, but it's not aligned properly if the textPanel is there too. Probably
		 * boundary issues?		
		 */
		JPanel textPanel = new JPanel();
		this.add(textPanel);		
		textPanel.setLayout(null);
		textPanel.setBounds(27, 650, 800, 200);
		
		/*
		 * Then write some introduction text in the box and add it to the
		 * text panel
		 */
		textField = new TextArea("Loading...", 0, 0);
		textPanel.add(textField);
		
		if(JLeibniz.isDebug) JLeibniz.outWriter.write("Textbox set up.\n");
		
		/*
		 * Finally, add width and height of the text box. It's placed at 0, 0
		 * relative to its panel (the text panel), so actually at 27, 650 of the
		 * main window
		 */
		textField.setBounds(0, 0, 800, 75);
		
		this.setVisible(true);
		
		if(JLeibniz.isDebug) JLeibniz.outWriter.write("Main Frame created.\n");
		
		/*
		 * Add a mouse- and mouse-motion handler, so we can handle clicking
		 * and moving of the mouse.
		 */
		mouseHandler = new MouseEventHandler();
		mouseMotionHandler = new MouseMotionHandler();
		this.addMouseListener(mouseHandler);
		this.addMouseMotionListener(mouseMotionHandler);
		
		if(JLeibniz.isDebug) JLeibniz.outWriter.write("Mouse Handler added.\n");
		
	}
	
	/**
	 * Rescales the image, if it does not fit within a 150x50 pixel area.
	 * 
	 * @param icon	ImageIcon of the image to be resized
	 * @param image	The image to be resized
	 * @return		A resized image
	 */
	private Image rescaleImage(ImageIcon icon, Image image) {
		
		/*
		 * Resize the image:
		 * If the image's height is larger than 50 pixels, and the height
		 * is larger than the width, rescale the image to be 50 pixels high.
		 */
		if(icon.getIconHeight() > 50 &&
				icon.getIconHeight() >= icon.getIconWidth()) {
			
			/*
			 *  -1 means the value for the width is replaced by one that
			 *  preserves aspect ratio
			 */
			return image.getScaledInstance(-1, 50, 0);
		}
		/*
		 *  if the width was the larger value (and is larger than 150),
		 *  rescale the image to be 150 pixels wide 
		 */
		else if(icon.getIconWidth() > 150) {
			return image.getScaledInstance(150, -1, 0);
		}
		
		/*
		 * If we get here, the image didn't need to be resized, so we just
		 * return the original image.
		 */
		return image;
	}
	
	/**
	 * This is called whenever the player picks up a new item. The inventory
	 * is checked for free spaces and the new item is drawn to the buffer 
	 * at the appropriate place.
	 * 
	 * @param newItem
	 */
	public void displayInv(Object newItem) {
		
		if(item1 == null) {
			// if there's no item in this place, put the new item here
			invImageIcon1 = new ImageIcon(newItem.getImagePath());
			invImage1 = invImageIcon1.getImage();
			
			// rescale image if necessary
			invImage1 = rescaleImage(invImageIcon1, invImage1);
			
			// now set the item's new position
			item1 = newItem;
			item1.setX(860);
			item1.setY(100);
		}
		else if (item2 == null){
			
			invImageIcon2 = new ImageIcon(newItem.getImagePath());
			invImage2 = invImageIcon2.getImage();
			
			invImage2 = rescaleImage(invImageIcon2, invImage2);
			
			item2 = newItem;
			item2.setX(860);
			item2.setY(160);
		}
		else if (item3 == null) {
			
			invImageIcon3 = new ImageIcon(newItem.getImagePath());
			invImage3 = invImageIcon3.getImage();
			
			invImage3 = rescaleImage(invImageIcon3, invImage3);
			
			item3 = newItem;
			item3.setX(860);
			item3.setY(220);
		}
		else if (item4 == null) {
			invImageIcon4 = new ImageIcon(newItem.getImagePath());
			invImage4 = invImageIcon4.getImage();
			
			invImage4 = rescaleImage(invImageIcon4, invImage4);
			
			item4 = newItem;
			item4.setX(860);
			item4.setY(280);
		}
		else if (item5 == null) {
			invImageIcon5 = new ImageIcon(newItem.getImagePath());
			invImage5 = invImageIcon5.getImage();
			
			invImage5 = rescaleImage(invImageIcon5, invImage5);
			
			item5 = newItem;
			item5.setX(860);
			item5.setY(340);
		}
	}
	
	/**
	 * Called every frame to redraw the inventory to the buffer.
	 */
	public void redisplayInv() {
		if(invImage1 != null) {
			
			// If the item is in combining mode, make it follow the mouse
			if(mouseHandler.action == mouseHandler.COMBINING &&
					mouseHandler.source.equals(item1)) {			
				
					item1.setX(((MouseMotionHandler) mouseMotionHandler).getX());
					item1.setY(((MouseMotionHandler) mouseMotionHandler).getY());
				
			}
			
			dbg.drawImage(invImage1, item1.getX(), item1.getY(), null);	
			
			
		}
		if(invImage2 != null) {
			
			if(mouseHandler.action == mouseHandler.COMBINING &&
					mouseHandler.source.equals(item2)) {			
				
					item2.setX(((MouseMotionHandler) mouseMotionHandler).getX());
					item2.setY(((MouseMotionHandler) mouseMotionHandler).getY());
				
			}
			
			dbg.drawImage(invImage2, item2.getX(), item2.getY(), null);
		}
		if(invImage3 != null) {
			
			if(mouseHandler.action == mouseHandler.COMBINING &&
					mouseHandler.source.equals(item3)) {			
				
					item3.setX(((MouseMotionHandler) mouseMotionHandler).getX());
					item3.setY(((MouseMotionHandler) mouseMotionHandler).getY());
				
			}
			
			dbg.drawImage(invImage3, item3.getX(), item3.getY(), null);
		}
		if(invImage4 != null) {
			
			if(mouseHandler.action == mouseHandler.COMBINING &&
					mouseHandler.source.equals(item4)) {			
				
					item4.setX(((MouseMotionHandler) mouseMotionHandler).getX());
					item4.setY(((MouseMotionHandler) mouseMotionHandler).getY());
				
			}
			
			dbg.drawImage(invImage4, item4.getX(), item4.getY(), null);
		}
		if(invImage5 != null) {
			
			if(mouseHandler.action == mouseHandler.COMBINING &&
					mouseHandler.source.equals(item5)) {			
				
					item5.setX(((MouseMotionHandler) mouseMotionHandler).getX());
					item5.setY(((MouseMotionHandler) mouseMotionHandler).getY());
				
			}
			
			dbg.drawImage(invImage5, item5.getX(), item5.getY(), null);
		}
	}
	
	/**
	 * Called when an item is removed from the inventory. Sets the
	 * "pointers" to null again.
	 * 
	 * @param obj
	 */
	public void removeFromInv(Object obj) {
		
		/*
		 *  TODO: If an item at a lower position is removed, make items at
		 *  higher positions take the lower place.
		 */
		if(obj.equals(item1)) {
			invImage1 = null;
			item1 = null;
		}
		else if(obj.equals(item2)) {
			invImage2 = null;
			item2 = null;
		}
		else if(obj.equals(item3)) {
			invImage3 = null;
			item3 = null;
		}
		else if(obj.equals(item4)) {
			invImage4 = null;
			item4 = null;
		}
		else if(obj.equals(item5)) {
			invImage5 = null;
			item5 = null;
		}
		
	}
	
	
	/**
	 * Updates variables used in renderScreen().
	 */
	public void updateGame() {
		// get the player's current room
		currentRoom = JLeibniz.getPlayer().getCurrentRoom();
		
		// get all items in the room
		roomItems = currentRoom.getItems();
		
		// get all items in the player's inventory
		playerItems = JLeibniz.getPlayer().getInventory();
		
		// draw output to the text box
		textField.setText(JLeibniz.output);
		
		// Display the graphics of the room the player currently is in
		roomImageIcon = new ImageIcon(currentRoom.getImagePath());
		roomImage = roomImageIcon.getImage();
		
		/* ------------------------------------------------------
		 * TODO: All of this should probably get its own method.
		 * ------------------------------------------------------
		 */
		
		/* movement - if the player isn't at the target location yet, move him closer
		 * 
		 * TODO: Pathfinding.
		 */
		player = JLeibniz.getPlayer();
		
		// if the player destination is different from his current position, move him
		/*if(player.getX() != player.getDestX() || player.getY() != player.getDestY()) {

			if(player.getX() < player.getDestX()) {
				player.setX(player.getX()+1);
			}
			else if(player.getX() > player.getDestX()) {
				player.setX(player.getX()-1);
			}
			if(player.getY() < player.getDestY()) {
				player.setY(player.getY()+1);
			}
			else if(player.getY() > player.getDestY()) {
				player.setY(player.getY()-1);
			}	
		}*/
		if(player.moving/*player.getX() != player.getDestX() || player.getY() != player.getDestY()*/) {
			
			// If we don't have a path to walk yet, find one
			if(!pathFound) {
			
//System.out.println("Attempting to find path from "+player.getX()/4+","+player.getY()/4+" to "+player.getDestX()/4+" "+player.getDestY()/4);
				
				AStar findPath = new AStar(JLeibniz.map, JLeibniz.map.getNode(player.getX()/10, player.getY()/10), JLeibniz.map.getNode(player.getDestX()/10, player.getDestY()/10));
				ArrayList<Node> path = (ArrayList<Node>) findPath.runAStar();
								
				// Upscale the path to actual screen resolution
				for(Node n : path) {				
					
					upscaledPath.add(new Node(n.getX()*10, n.getY()*10, true));				
				}
				
//System.out.println("Upscaled path: "+upscaledPath.toString());			
			
				pathFound = true;
			
			}
			else {
				/*
				 *  If we do have a path, traverse it.
				 *  If there are waypoints left in the path, go to
				 *  them one after the other. If we have reached the end
				 *  of the path, stop moving.
				 */
				if(!upscaledPath.isEmpty()) {				

					player.setX(upscaledPath.get(0).getX());
					player.setY(upscaledPath.get(0).getY());
					upscaledPath.remove(0);
				}	
				else {
					player.moving = false;
					pathFound = false;
				}
			}
			
		}
		// if player destination and location are the same, the player is not moving
		else {
			player.moving = false;
			pathFound = false;
		}
		
		/*
		 *  If the player is not moving, but the actionActive flag is still true,
		 *  that means the player clicked on something and there's still an action
		 *  to do, like picking up an object. If so, do it now.
		 */
		if(player.moving == false && mouseHandler.actionActive == true) {
			
			// handle mouse clicking and maybe do an action
			mouseHandler.clickedRoom(mouseHandler.mouseX, mouseHandler.mouseY);
			
			// action is done
			mouseHandler.actionActive = false;						
		}
	
	}
	
	/**
	 * This draws to an image buffer. First phase of double-buffering.
	 * @throws IOException 
	 */
	public void renderScreen() throws IOException {
		
		// create the buffer, same size as our window
		if(dbImage == null) {
			
			dbImage = createImage(1024, 768);
			
			if(dbImage == null) {
				if(JLeibniz.isDebug) JLeibniz.outWriter.write("Failed to create dbImage.\n");
				return;
			}		
			else {
				dbg = dbImage.getGraphics();
			}
		}
		
		// clear the buffer image
		dbg.clearRect(0, 0, 1024, 768);
		
		// now comes all drawing stuff		
		
		dbg.drawImage(roomImage, 30, 50, null);
		
		/* Display all objects that are in the current room.
		 * 		
		 * iterate over the items and for each one get its image path,
		 * create an image for it and then draw it to the appropriate position
		 * 
		 * TODO: I COULD separate this so render stuff and logic stuff is divided
		 * clearly. I could put all objectImages in a list in updateGame()
		 * and then draw those from here. However I'm too lazy.
		 */
		if(!roomItems.isEmpty()) {
			for(String key: roomItems.keySet()) {
				objectImageIcon = new ImageIcon(roomItems.get(key).getImagePath());
				objectImage = objectImageIcon.getImage();
				dbg.drawImage(objectImage, roomItems.get(key).getX(), roomItems.get(key).getY(), null);
			}
		}

		// redraw the inventory
		redisplayInv();
		
		// draw the player character
		//User player = JLeibniz.getPlayer();
		ImageIcon playerImageIcon = new ImageIcon("res/user.png");
		Image playerImage = playerImageIcon.getImage();
		dbg.drawImage(playerImage, player.getX(), player.getY(), null);		
	}
	
	/**
	 * Draws the buffer to the screen. Second phase of double-buffering.
	 */
	public void paint(Graphics g) {		
	
		// draw the buffer to the screen
		if(dbImage != null) {
			g.drawImage(dbImage, 0, 0, null);
		}		
	}
}
